#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>

#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)
/* union semun is defined by including <sys/sem.h> */
#else
/* according to X/OPEN we have to define it ourselves */
union semun {
      int val;                  /* value for SETVAL */
      struct semid_ds *buf;     /* buffer for IPC_STAT, IPC_SET */
      unsigned short *array;    /* array for GETALL, SETALL */
                                /* Linux specific part: */
      struct seminfo *__buf;    /* buffer for IPC_INFO */
} arg;
#endif

#include "ProcessList.h"
#include "parser.h"

#define SHM_KEY	123456557
#define SEM_KEY	456123462

#define MEM_SIZE 1024
#define PAGE_SIZE 128

#define TIMER_INTERRUPTION 	1
#define DISC_INTERRUPTION 	5
#define GET_SW_INTERRUPTION	36
#define ILLEGAL_MEMORY_ACCESS	10
#define CONSOLE_INTERRUPTION	9


#define MAX_REGISTERS	16

typedef struct
{
	pthread_mutex_t processorMutex;
	pthread_mutex_t consoleMutex;
	pthread_mutex_t timerMutex;
	pthread_mutex_t discMutex;
	pthread_mutex_t intMutex;
	pthread_mutex_t memoryMutex;
} Semaphores;

struct sembuf semOps;

ProcessList *readyList = NULL;

typedef struct
{
	int memory[MEM_SIZE];
	int PC;
	int baseRegister;
	int registers[MAX_REGISTERS];
	int RD;
	char IR[4]; // changed char to int
} ProcessorHardware;
	

void * runProcessor(Semaphores *semaphores);
void * runConsole(Semaphores *semaphores);
void * runTimer(Semaphores *semaphores);
void * runDisc(Semaphores *semaphores);

void run_kernel(int interruptionNumber, ProcessorHardware *hardware, Semaphores *semaphores);

void set_interruption(pthread_mutex_t *intMutex, int number);
int get_interruption();

int baseRegister, limitRegister; // registradores de limite memoria

int *consoleBuffer;

int Z = 0;
int E = 1; // flags
int L = 2;

int slice = 10;

short interruption;

int reg[MAX_REGISTERS];
int flag[3];

FILE *pfile;

int semid;

int main ()
{
	int shmid;
	if((shmid = shmget(SHM_KEY, sizeof(char), IPC_CREAT | 0666)) == -1)
	{
		perror("Error on shmget()\n");
		exit(-1);
	}
	
	if((consoleBuffer = (int *) shmat(shmid, NULL, 0)) == -1)
	{
		perror("Error on shmat(): \n");
		exit(-1);
	}
	
	*consoleBuffer = -1;

	if((semid = semget(SEM_KEY, 1, IPC_CREAT | 0666)) == -1)
	{
		printf("Error on semget()\n");
		exit(-1);
	}
	
	arg.val = 1;
	
	if(semctl(semid, 0, SETVAL, arg) == -1)
	{
		perror("Error on semctl()\n");
		exit(-1);
	}	
	
	
	pthread_t tid[4];
	Semaphores semaphores;
	
	pthread_mutex_init(&semaphores.memoryMutex, NULL);	
	
	pthread_mutex_init(&semaphores.discMutex, NULL);
	
	pthread_mutex_init(&semaphores.intMutex, NULL);
	
	pthread_mutex_init(&semaphores.timerMutex, NULL);
	
	pthread_create(&tid[0], NULL, (void *) &runProcessor, &semaphores);		
	pthread_create(&tid[1], NULL, (void *) &runConsole, &semaphores);
	pthread_create(&tid[2], NULL, (void *) &runTimer, &semaphores);	
	pthread_create(&tid[3], NULL, (void *) &runDisc, &semaphores);
		
	while(1);
	
	return 0;
}

void super_memory_write(int address, int a, int b, int c, int d, ProcessorHardware *hardware, Semaphores *semaphores)
{
	pthread_mutex_lock(&semaphores->memoryMutex);
	
	hardware->memory[address] = (d<<24)+(c<<16)+(b<<8)+a;
	
	pthread_mutex_unlock(&semaphores->memoryMutex);	
}

void write_data_to_memory(int add, int data, ProcessorHardware *hardware)
{
	hardware->memory[add] = data;
}

void init_memory(ProcessorHardware *hardware, Semaphores *semaphores)
{
	super_memory_write(0, 'L', 'M', 0, 7, hardware, semaphores);
	super_memory_write(1, 'L', 'C', 1, 30, hardware, semaphores);
	super_memory_write(2, 'A', 'D', 1, 0, hardware, semaphores);
	super_memory_write(3, 'S', 'U', 0, 1, hardware, semaphores);
	super_memory_write(4, 'W', 'M', 0, 129, hardware, semaphores);
	super_memory_write(5, 'W', 'M', 1, 36, hardware, semaphores);
	super_memory_write(6, 'J', 'P', 'A',0, hardware, semaphores);
	write_data_to_memory(7, 15, hardware);
	
	super_memory_write(128, 'L', 'M', 0, 7, hardware, semaphores);
	super_memory_write(129, 'L', 'C', 1, 30, hardware, semaphores);
	super_memory_write(130, 'A', 'D', 1, 0, hardware, semaphores);
	super_memory_write(131, 'S', 'U', 0, 1, hardware, semaphores);
	super_memory_write(132, 'W', 'M', 0, 32, hardware, semaphores);
	super_memory_write(133, 'W', 'M', 1, 36, hardware, semaphores);
	super_memory_write(134, 'J', 'P', 'A',0, hardware, semaphores);
	write_data_to_memory(135, 15, hardware);
	
	super_memory_write(256, 'L', 'M', 0, 7, hardware, semaphores);
	super_memory_write(257, 'L', 'C', 1, 30, hardware, semaphores);
	super_memory_write(258, 'A', 'D', 1, 0, hardware, semaphores);
	super_memory_write(259, 'S', 'U', 0, 1, hardware, semaphores);
	super_memory_write(260, 'W', 'M', 0, 32, hardware, semaphores);
	super_memory_write(261, 'W', 'M', 1, 36, hardware, semaphores);
	super_memory_write(262, 'J', 'P', 'A',0, hardware, semaphores);
	write_data_to_memory(263, 15, hardware);
}

int memory_read(int address, ProcessorHardware hardware, Semaphores *semaphores)
{
	pthread_mutex_lock(&semaphores->memoryMutex);
	if (address >= PAGE_SIZE || address < 0)
	{
		printf("\nIllegal memory access: %d.", address);
		pthread_mutex_unlock(&semaphores->memoryMutex);
		set_interruption(&(semaphores->intMutex), ILLEGAL_MEMORY_ACCESS);
		return 0;
	}
	else
	{
		pthread_mutex_unlock(&semaphores->memoryMutex);
		return hardware.memory[hardware.baseRegister + address];
	}
}

void memory_write(int address, int data, ProcessorHardware *hardware, Semaphores *semaphores)
{
	pthread_mutex_lock(&semaphores->memoryMutex);
	if (address >= PAGE_SIZE || address < 0)
	{
		printf("\nIllegal memory access: %d.", address);
		pthread_mutex_unlock(&semaphores->memoryMutex);
		set_interruption(&(semaphores->intMutex), ILLEGAL_MEMORY_ACCESS);
	}
	else
	{
		hardware->memory[hardware->baseRegister + address] = data;
		pthread_mutex_unlock(&semaphores->memoryMutex);
	}
	
}

void * runProcessor(Semaphores *semaphores)
{
	ProcessorHardware hardware;	
	short interruptionNumber;
	char *IR = hardware.IR;
	int *reg = hardware.registers;
	
	hardware.PC = 0;
	
	init_memory(&hardware, semaphores);	//init memory (system boot)	
	
	createProcess(&readyList, 1, 0, 20);
	createProcess(&readyList, 2, 128, 10);
	createProcess(&readyList, 3, 256, 40);
	
	while(1)
	{
		hardware.RD = memory_read(hardware.PC++, hardware, semaphores);
		     
		IR[3] = hardware.RD>>24;
		IR[2] = (hardware.RD>>16) & 255;
		IR[1] = (hardware.RD>>8) & 255;
		IR[0] = hardware.RD & 255;
	
		if ((IR[0]=='L') && (IR[1]=='M'))
		{	
			printf("INSTRUCTION %d: %c %c %d %d", (hardware.PC + hardware.baseRegister - 1), IR[0], IR[1], IR[2], IR[3]);
			reg[IR[2]] = memory_read(IR[3], hardware, semaphores);
			printf("\nRegister %d receives %d.", IR[2], reg[IR[2]]);
		}
		else
		if ((IR[0]=='L') && (IR[1]=='C'))
		{
			printf("INSTRUCTION %d: %c %c %d %d", (hardware.PC + hardware.baseRegister - 1), IR[0], IR[1], IR[2], IR[3]);
			printf("\nRegister %d receives %d.", IR[2], IR[3]);
			reg[IR[2]] = IR[3];
		}
		else
		if ((IR[0]=='W') && (IR[1]=='M'))
		{
			printf("INSTRUCTION %d: %c %c %d %d", (hardware.PC + hardware.baseRegister - 1), IR[0], IR[1], IR[2], IR[3]);
			printf("\nMemory[%d](%d) receives %d.", IR[3], IR[3] + hardware.baseRegister, reg[IR[2]]);
			memory_write(IR[3], reg[IR[2]], &hardware, semaphores);		

		}
		else
		if ((IR[0]=='S') && (IR[1]=='U'))
		{
			printf("INSTRUCTION %d: %c %c %d %d", (hardware.PC + hardware.baseRegister - 1), IR[0], IR[1], IR[2], IR[3]);
			printf("\nRegister %d receives %d - %d = %d.", IR[2], reg[IR[2]], reg[IR[3]], reg[IR[2]] - reg[IR[3]]);
			reg[IR[2]] = reg[IR[2]] - reg[IR[3]];
			
		}
		else
		if ((IR[0]=='A') && (IR[1]=='D'))
		{
			printf("INSTRUCTION %d: %c %c %d %d", (hardware.PC + hardware.baseRegister - 1), IR[0], IR[1], IR[2], IR[3]);
			printf("\nRegister %d receives %d + %d = %d.", IR[2], reg[IR[2]], reg[IR[3]], reg[IR[2]] + reg[IR[3]]);
			reg[IR[2]] = reg[IR[2]] + reg[IR[3]];
		}
		else
		if ((IR[0]=='D') && (IR[1]=='E') && (IR[2]=='C'))
		{
			printf("INSTRUCTION %d: %c %c %c %d", (hardware.PC + hardware.baseRegister - 1), IR[0], IR[1], IR[2], IR[3]);
			printf("\nRegister %d receives %d.", IR[3], reg[IR[3]] - 1);
			reg[IR[3]] = reg[IR[3]] - 1;
		}
		else
		if ((IR[0]=='I') && (IR[1]=='N') && (IR[2]=='C'))
		{
			printf("INSTRUCTION %d: %c %c %c %d", (hardware.PC + hardware.baseRegister - 1), IR[0], IR[1], IR[2], IR[3]);
			printf("\nRegister %d receives %d.", IR[3], reg[IR[3]] + 1);
			reg[IR[3]] = reg[IR[3]] + 1;
		}
		else
		if ((IR[0]=='C') && (IR[1]=='P'))
		{
			printf("INSTRUCTION %d: %c %c %d %d", (hardware.PC + hardware.baseRegister - 1), IR[0], IR[1], IR[2], IR[3]);
			printf("\nComparing %d with %d.", reg[IR[2]], reg[IR[3]]);
			if (reg[IR[2]] == 0) flag[Z] = 1; else flag[Z] = 0;
			if (reg[IR[2]] == reg[IR[3]]) flag[E] = 1; else flag[E] = 0;
			if (reg[IR[2]] < reg[IR[3]]) flag[L] = 1; else flag[L] = 0;
		}
		else
		if ((IR[0]=='J') && (IR[1]=='P') && (IR[2]=='A'))
		{
			printf("INSTRUCTION %d: %c %c %c %d", (hardware.PC + hardware.baseRegister - 1), IR[0], IR[1], IR[2], IR[3]);
			printf("\nJumping to: %d (%d).", IR[3], hardware.baseRegister + IR[3]);
			hardware.PC = IR[3];
		}
		else
		if ((IR[0]=='J') && (IR[1]=='P') && (IR[2]=='Z'))
		{
			printf("INSTRUCTION %d: %c %c %c %d", (hardware.PC + hardware.baseRegister - 1), IR[0], IR[1], IR[2], IR[3]);
			if (flag[Z] == 1)
				hardware.PC = IR[3];
		}
		else
		if ((IR[0]=='J') && (IR[1]=='P') && (IR[2]=='E'))
		{
			printf("INSTRUCTION %d: %c %c %c %d", (hardware.PC + hardware.baseRegister - 1), IR[0], IR[1], IR[2], IR[3]);
			if (flag[E] == 1)
			hardware.PC = IR[3];
		}
		else
		if ((IR[0]=='J') && (IR[1]=='P') && (IR[2]=='L'))
		{
			printf("INSTRUCTION %d: %c %c %c %d", (hardware.PC + hardware.baseRegister - 1), IR[0], IR[1], IR[2], IR[3]);
			if (flag[L] == 1)
			hardware.PC = IR[3];
		}
		else
		if (IR[0]=='I'&&IR[1]=='N'&&IR[2]=='T')
		{
			printf("INSTRUCTION %d: %c %c %c %d", (hardware.PC + hardware.baseRegister - 1), IR[0], IR[1], IR[2], IR[3]);
			run_kernel(IR[3], &hardware, semaphores);
		}
		else
		{
			printf("INSTRUCTION %d (%d): UNKNOWN INSTRUCTION\n", hardware.PC - 1, hardware.PC + hardware.baseRegister - 1);
		}
		
		if ((interruptionNumber = get_interruption()) != 0)
		{
			run_kernel(interruptionNumber, &hardware, semaphores);
			pthread_mutex_unlock(&semaphores->intMutex);
		}
		
		printf("\n\n");
		usleep(500000);
	}		
}

void save_context(ProcessList *processList, ProcessorHardware *hardware)
{
	processList->process.PC = hardware->PC;
	
	int i;
	for(i = 0; i < MAX_REGISTERS; i++)
		processList->process.registers[i] = hardware->registers[i];
}

void restore_context(ProcessList *processList, ProcessorHardware *hardware)
{
	hardware->PC = processList->process.PC;
	hardware->baseRegister = processList->process.baseRegister;
	
	int i;
	for(i = 0; i < MAX_REGISTERS; i++)
		hardware->registers[i] = processList->process.registers[i];
}

void switch_context(ProcessList **processList)
{
	*processList = (*processList)->next;
}	

void run_kernel(int interruptionNumber, ProcessorHardware *hardware, Semaphores *semaphores)
{
	fprintf(stdout, "\nKernel called for interruption number %d\n", interruptionNumber);
	
	save_context(readyList, hardware);
	
	struct sembuf sops;
	
	sops.sem_num = 0;
	sops.sem_op = 1;
	sops.sem_flg = 0;	
	
	switch(interruptionNumber)
	{
		case TIMER_INTERRUPTION:
			switch_context(&readyList);
			printf("CPU runs: %d\n", readyList->process.PID);
			slice = readyList->process.timeSlice;
			pthread_mutex_unlock(&semaphores->timerMutex);
			break;
		
		case ILLEGAL_MEMORY_ACCESS:
			printf("Killing process %d for illegal memory access.\n", readyList->process.PID);
			removeProcess(&readyList);
			break;
		
		case CONSOLE_INTERRUPTION:
			printf("Console Interruption. Loading process on line: %d...\n", *consoleBuffer);
			
			/*int byte[4];
				
			pthread_mutex_lock(&semaphores->discMutex);
			
			int i = 512;
			
			int quit = 0;
			int line = *(consoleBuffer);

			while(quit != 1)
			{
				parserFile(line++, pfile, byte);
				
				if ((byte[0] == 255) && (byte[1] == 255) && (byte[2] == 255) && (byte[3] == 255))
				{	
					quit = 1;
				}
				else
				{
					printf("AQUI\n");
					//super_memory_write(i++, byte[3], byte[2], byte[1], byte[0], hardware, semaphores);
					//*consoleBuffer += 1;
				}
				
				printf("Loading on mem %d: %c %c %d %d\n", i, byte[3], byte[2], byte[1], byte[0]);
			}*/

			//createProcess(&readyList, 5, 512);
			
			if(semop(semid, &sops, 1) == -1)
			{
				perror("Error on semop()\n");
				exit(-1);
			}
			
			//pthread_mutex_unlock(&semaphores->discMutex);
			break;		
			
		case DISC_INTERRUPTION:
			//moveProcess(processLists.blocked, processLists.ready);
			break;
			
		case GET_SW_INTERRUPTION:
			//moveProcess(processLists.ready, processLists.blocked#define ILLEGAL_MEMORY_ACCESS	10);
			break;		
		
		default:
			printf("Unknown Interruption\n");
	}
	
	restore_context(readyList, hardware);
}

void set_interruption(pthread_mutex_t *intMutex, int number)
{
	pthread_mutex_lock(intMutex);
	interruption = number;
}
	
int get_interruption()
{
	
	int interruptionNumber = interruption;
	interruption = 0;
	return interruptionNumber;
}
	

void * runConsole(Semaphores *semaphores)
{
	
	while(1)
	{
		printf("CONSOLE...\n");
		if(*consoleBuffer != -1) //generate interruption
		{
			printf("\n\nCONSOLE INTERRUPTION...\n\n");
			set_interruption(&semaphores->intMutex, CONSOLE_INTERRUPTION);			
		}		
		sleep(1);
	}
}

void * runTimer(Semaphores *semaphores)
{
	int counter;
	while(1)
	{
		pthread_mutex_lock(&semaphores->timerMutex);
		counter = slice;
		
		while(slice > 0)
		{
			usleep(500000);
			slice--;
		}
		
		printf("\n\nTIMER requested an interruption\n\n");
		set_interruption(&semaphores->intMutex, TIMER_INTERRUPTION);
	}
}

void * runDisc(Semaphores *semaphores)
{
	pfile = fopen("disk.txt", "r+");
	
	if(pfile == NULL){
		printf("Error Reading File");
		exit(1);
	}
	
	while(1)
	{
		//pthread_mutex_lock(&semaphores->discMutex);		
		//sleep(2);
		//set_interruption(&semaphores->intMutex, DISC_INTERRUPTION);
	}
}
